<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="swexX" id="swexX"><span data-lake-id="u1414382a" id="u1414382a">典型回答</span></h1>
  <p data-lake-id="ud9939155" id="ud9939155"><span data-lake-id="ub8555268" id="ub8555268"><br>
    Java通过双亲委派模型保证了java核心包中的类不会被破坏，但破坏双亲委派能够脱离加载范围的限制，增强第三方组件的能力。</span></p>
  <p data-lake-id="ua7fb1a51" id="ua7fb1a51"><span data-lake-id="udeb7739e" id="udeb7739e">​</span><br></p>
  <p data-lake-id="ubf48bb61" id="ubf48bb61"><br></p>
  <p data-lake-id="u2a4ba1e6" id="u2a4ba1e6"><span data-lake-id="u2708391b" id="u2708391b">但是我们虽然可以通过破坏双亲委派屏蔽Bootstrap ClassLoader，但无法重写</span><code data-lake-id="u45475df5" id="u45475df5"><span data-lake-id="u5bdea4ac" id="u5bdea4ac">java.</span></code><span data-lake-id="uddad198b" id="uddad198b">包下的类，如</span><code data-lake-id="ueeb44737" id="ueeb44737"><span data-lake-id="ub1814835" id="ub1814835">java.lang.String</span></code><span data-lake-id="u75331388" id="u75331388">。</span></p>
  <p data-lake-id="ue0d17ff8" id="ue0d17ff8"><br></p>
  <p data-lake-id="u959a6fdb" id="u959a6fdb"><span data-lake-id="u5eb7fc98" id="u5eb7fc98">我们知道，要破坏双亲委派模型是需要</span><code data-lake-id="ubd07d99d" id="ubd07d99d"><span data-lake-id="uc2774830" id="uc2774830">extends ClassLoader</span></code><span data-lake-id="ud1175133" id="ud1175133">并重写其中的</span><code data-lake-id="uba8ef0c4" id="uba8ef0c4"><span data-lake-id="uc53189b3" id="uc53189b3">loadClass()</span></code><span data-lake-id="uef5e8ca1" id="uef5e8ca1">和</span><code data-lake-id="u42eae6fd" id="u42eae6fd"><span data-lake-id="u7763c4ed" id="u7763c4ed">findClass()</span></code><span data-lake-id="u2a08d364" id="u2a08d364">方法。</span></p>
  <p data-lake-id="u4d0c295d" id="u4d0c295d"><span data-lake-id="u027dfdd3" id="u027dfdd3"><br>
    之所以无法替换</span><code data-lake-id="uffba8994" id="uffba8994"><span data-lake-id="u35551ec4" id="u35551ec4">java.</span></code><span data-lake-id="u0519bef0" id="u0519bef0">包的类，主要原因是即使我们破坏双亲委派模型，依然需要调用父类中（</span><code data-lake-id="ud95dd679" id="ud95dd679"><span data-lake-id="u8731288d" id="u8731288d">java.lang.ClassLoader.java</span></code><span data-lake-id="u6b01914f" id="u6b01914f">）的</span><code data-lake-id="u5db984b7" id="u5db984b7"><span data-lake-id="u1c993ffa" id="u1c993ffa">defineClass()</span></code><span data-lake-id="u2affaf59" id="u2affaf59">方法来把字节流转换为一个JVM识别的class。而</span><code data-lake-id="uf6500a48" id="uf6500a48"><span data-lake-id="ua8b7ce79" id="ua8b7ce79">defineClass()</span></code><span data-lake-id="u3b4cd4f4" id="u3b4cd4f4">方法中通过</span><code data-lake-id="u9e19fc8f" id="u9e19fc8f"><span data-lake-id="uaddf85a5" id="uaddf85a5">preDefineClass()</span></code><span data-lake-id="ue138363e" id="ue138363e">方法限制了类全限定名不能以</span><code data-lake-id="u38a0bfa0" id="u38a0bfa0"><span data-lake-id="u917621f0" id="u917621f0">java.</span></code><span data-lake-id="u31eb3173" id="u31eb3173">开头。</span></p>
  <p data-lake-id="ued877252" id="ued877252"><span data-lake-id="u52b41c24" id="u52b41c24">​</span><br></p>
  <p data-lake-id="u5e58b8a9" id="u5e58b8a9"><span data-lake-id="uc16a05d8" id="uc16a05d8">如下代码所示：</span></p>
  <p data-lake-id="ueb0702ce" id="ueb0702ce"><br></p>
  <pre lang="java"><code>
//将字节流转换成jvm可识别的java类
  protected final Class&lt;?&gt; defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);//检查类全限定名是否有效
        String source = defineClassSourceLocation(protectionDomain);
        Class&lt;?&gt; c = defineClass1(name, b, off, len, protectionDomain, source);//调用本地方法，执行字节流转JVM类的逻辑。
        postDefineClass(c, protectionDomain);
        return c;
    }

//检查类名的有效性
 private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);
        if ((name != null) &amp;&amp; name.startsWith("java.")) { //禁止替换以java.开头的类文件
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }
</code></pre>
  <p data-lake-id="ub12e8fc8" id="ub12e8fc8"><br></p>
  <p data-lake-id="u3846b736" id="u3846b736"><span data-lake-id="u2a729c4d" id="u2a729c4d">注意，</span><code data-lake-id="u808b084e" id="u808b084e"><span data-lake-id="u793c5078" id="u793c5078">defineClassX</span></code><span data-lake-id="uff6f9de6" id="uff6f9de6">三兄弟是三个本地方法，用于不同参数长度的方法调用。</span></p>
  <p data-lake-id="uf14a3c4e" id="uf14a3c4e"><br></p>
  <pre lang="java"><code>
 private native Class&lt;?&gt; defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class&lt;?&gt; defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class&lt;?&gt; defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);
</code></pre>
  <p data-lake-id="u1dfbfe59" id="u1dfbfe59"><br></p>
  <p data-lake-id="u0fe71fb0" id="u0fe71fb0"><span data-lake-id="u3a8d64a6" id="u3a8d64a6">对应到JDK源码中分别为：</span></p>
  <p data-lake-id="ua6e953a8" id="ua6e953a8"><br></p>
  <pre lang="java"><code>
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass0(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd)
                                        
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
                                    
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass2(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jobject data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
</code></pre>
  <p data-lake-id="u92aae5de" id="u92aae5de"><br></p>
  <p data-lake-id="u3376342b" id="u3376342b"><span data-lake-id="ube3bc30b" id="ube3bc30b">这三个C++方法会调用到</span><code data-lake-id="u3ac61c7b" id="u3ac61c7b"><span data-lake-id="uc524b8cf" id="uc524b8cf">SystemDictionary::resolve_from_stream</span></code><span data-lake-id="u5a60188a" id="u5a60188a">检查全限定名是否包含</span><code data-lake-id="uf52aec05" id="uf52aec05"><span data-lake-id="u123f0b21" id="u123f0b21">java.</span></code></p>
  <p data-lake-id="ufce572ca" id="ufce572ca"><br></p>
  <pre lang="java"><code>
klassOop SystemDictionary::resolve_from_stream(Symbol* class_name,
                                               Handle class_loader,
                                               Handle protection_domain,
                                               ClassFileStream* st,
                                               bool verify,
                                               TRAPS) {
 ...//省略无关代码，以下是并检查全限定名，若包含java.，则抛出异常。
 const char* pkg = "java/";
  if (!HAS_PENDING_EXCEPTION &amp;&amp;
      !class_loader.is_null() &amp;&amp;
      parsed_name != NULL &amp;&amp;
      !strncmp((const char*)parsed_name-&gt;bytes(), pkg, strlen(pkg))) {
    ResourceMark rm(THREAD);
    char* name = parsed_name-&gt;as_C_string();
    char* index = strrchr(name, '/');
    *index = '\0';
    while ((index = strchr(name, '/')) != NULL) {
      *index = '.';
    }
    const char* fmt = "Prohibited package name: %s";
    size_t len = strlen(fmt) + strlen(name);
    char* message = NEW_RESOURCE_ARRAY(char, len);
    jio_snprintf(message, len, fmt, name);
    Exceptions::_throw_msg(THREAD_AND_LOCATION,
      vmSymbols::java_lang_SecurityException(), message);
  }
}
</code></pre>
  <p data-lake-id="udcedd295" id="udcedd295"><span data-lake-id="u723774e7" id="u723774e7"><br>
    但是，如果破坏双亲委派的时候自己将字节流转换为一个jvm可识别的class，那确实绕过</span><code data-lake-id="u268e8e88" id="u268e8e88"><span data-lake-id="u5b4ad3e5" id="u5b4ad3e5">defineClass()</span></code><span data-lake-id="u0a73b085" id="u0a73b085">中的校验全限定名的逻辑，也就可以改写</span><code data-lake-id="uecf3d1b0" id="uecf3d1b0"><span data-lake-id="u96b78248" id="u96b78248">java.lang.String</span></code><span data-lake-id="u0a8ff4a1" id="u0a8ff4a1">，并加载到JVM中。</span></p>
 </body>
</html>